package org.unit.testmgmt.generator.wizard;

import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ICheckStateProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.unit.testmgmt.base.IUTMConstants;
import org.unit.testmgmt.generator.GeneratorUtils;
import org.unit.testmgmt.preferences.UTMPreferences;
import org.unit.testmgmt.tml.Method;
import org.unit.testmgmt.tml.Param;
import org.unit.testmgmt.tml.Result;
import org.unit.testmgmt.tml.Settings;
import org.unit.testmgmt.tml.Test;
import org.unit.testmgmt.tml.Testprio;
import org.unit.testmgmt.tml.UTMModel;
import org.unit.testmgmt.ui.generator.wizard.GeneratorWizardMainPage;

/**
 * The controller for the main page.
 * 
 * @author Robert Streng
 * 
 */
public class GeneratorWizardMain extends GeneratorWizardBase implements IUTMConstants {

	/**
	 * The filter for the methods.
	 * 
	 * @author Robert Streng
	 */
	private class ViewerFilterMethods extends ViewerFilter {

		private List<Method> tmlMethods;

		public ViewerFilterMethods(List<Method> tmlMethods) {
			this.tmlMethods = tmlMethods;
		}

		@Override
		public boolean select(Viewer viewer, Object parentElement, Object element) {

			IMethod method;
			if (element instanceof IMethod) {
				method = (IMethod) element;

				try {
					if (method.isConstructor()) {
						return false;
					}

					if (isExistingMethodsFilterSelected()) {
						if (tmlMethods != null && containMethod(method, tmlMethods)) {
							return false;
						}
					}

					if (!isMethodAllowed(method, isNameFilterSelected(), isModifierFilterSelected()))
						return false;

				}
				catch (JavaModelException e) {
					throw new RuntimeException(e);
				}

				return true;
			}
			else if (element instanceof IType && !(parentElement instanceof IType)) {
				return true;
			}

			return false;
		}
	}

	/**
	 * The check state provider for the methods.
	 * 
	 * @author Robert Streng
	 */
	class CheckStateProvider implements ICheckStateProvider {

		@Override
		public boolean isChecked(Object element) {
			if (getCheckedMethods().contains(element)) {
				return true;
			}

			if (element instanceof IJavaElement) {
				IJavaElement javaElement = (IJavaElement) element;
				if (isParentChecked(javaElement.getParent())) {
					return true;
				}
			}

			return false;
		}

		private boolean isParentChecked(IJavaElement element) {
			if (element == null) {
				return false;
			}
			if (element instanceof IMethod) {
				if (getCheckedMethods().contains((IMethod) element)) {
					return true;
				}
			}

			return isParentChecked(element.getParent());
		}

		@Override
		public boolean isGrayed(Object element) {
			return getPage().getCheckboxTreeViewer().getGrayed(element);
		}
	}

	private ICompilationUnit testBase;
	private ICompilationUnit testClass;

	// filter-instances for the methods
	private Vector<String> nameFilterStartsWith = new Vector<String>();
	private Vector<String> nameFilterEquals = new Vector<String>();
	private Vector<String> nameFilterEndsWith = new Vector<String>();

	private boolean modifierFilterSelected = true;
	private boolean nameFilterSelected = true;
	private boolean existingMethodsFilterSelected = true;

	// checked methods
	private Vector<IMethod> checkedMethods = new Vector<IMethod>();
	private Vector<IMethod> baseClassMethods = null;
	private ViewerFilterMethods viewerFilterMethods;

	/**
	 * Constructor
	 * 
	 * @param model
	 * @param generatorWizardMainPage
	 */
	public GeneratorWizardMain(UTMModel model, GeneratorWizardMainPage generatorWizardMainPage) {
		super(model, generatorWizardMainPage);
		testBase = getModel().getUtmElements().getClassesAndPackages().getTestBase();
		testClass = getModel().getUtmElements().getClassesAndPackages().getTestClass();
	}

	@Override
	public GeneratorWizardMainPage getPage() {
		return (GeneratorWizardMainPage) super.getPage();
	}

	/**
	 * Returns if the method is in the method list and add the method if not exists.
	 * 
	 * @param method
	 * @param tmlMethods
	 * @return true if method is in the method list
	 */
	protected boolean containMethod(IMethod method, List<Method> tmlMethods) {
		HashMap<IMethod, Method> methodMap = getModel().getMethodMap();

		if (methodMap.containsKey(method)) {
			if (methodMap.get(method) != null)
				return true;
			else
				return false;
		}

		Method tmlMethod = GeneratorUtils.getClosestMethod(method, tmlMethods);
		if (tmlMethod != null) {
			methodMap.put(method, tmlMethod);
			return true;
		}

		methodMap.put(method, null);
		return false;
	}

	/**
	 * Initializes the name filters.
	 */
	private void initNameFilters() {
		for (String filter : UTMPreferences.getTestMethodFilterName()) {
			if (filter.startsWith("*")) {
				nameFilterEndsWith.add(filter.replaceFirst("*", ""));
			}
			else if (filter.endsWith("*")) {
				nameFilterStartsWith.add(filter.replace("*", ""));
			}
			else {
				nameFilterEquals.add(filter);
			}
		}
	}

	/**
	 * Returns if the method name is allowed.
	 * 
	 * @param method
	 * @return true if the method name is allowed
	 */
	private boolean isMethodNameAllowed(IMethod method) {
		String methodName = method.getElementName().replace(".java", "");

		Vector<String> startsWithFilter = getTestClassNameFilterStartsWith();
		Vector<String> equalsFilter = getTestClassNameFilterEquals();
		Vector<String> endsFilter = getTestClassNameFilterEndsWith();

		for (String filter : startsWithFilter) {
			if (methodName.startsWith(filter)) {
				return false;
			}
		}

		for (String filter : equalsFilter) {
			if (methodName.equalsIgnoreCase(filter)) {
				return false;
			}
		}

		for (String filter : endsFilter) {
			if (methodName.endsWith(filter)) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Return if the method is allowed.
	 * 
	 * @param method
	 * @param nameFilter
	 * @param modifierFilter
	 * @return true when the method is allowed
	 * @throws JavaModelException
	 */
	private boolean isMethodAllowed(IMethod method, boolean nameFilter, boolean modifierFilter)
			throws JavaModelException {
		if (modifierFilter && !isModifierAllowed(method)) {
			return false;
		}

		if (nameFilter && !isMethodNameAllowed(method)) {
			return false;
		}

		return true;
	}

	/**
	 * 
	 * @param method
	 * @return
	 * @throws JavaModelException
	 */
	private boolean isModifierAllowed(IMethod method) throws JavaModelException {
		String modifier = GeneratorUtils.getMethodModifier(method);

		for (String notAllowedModifier : UTMPreferences.getTestMethodFilterModifier()) {

			if (MOD_PUBLIC.equalsIgnoreCase(notAllowedModifier) && MOD_PUBLIC.equals(modifier)) {
				return false;
			}
			else if (MOD_PROTECTED.equalsIgnoreCase(notAllowedModifier)
					&& MOD_PROTECTED.equals(modifier)) {
				return false;
			}
			else if (MOD_PRIVATE.equalsIgnoreCase(notAllowedModifier)
					&& MOD_PRIVATE.equals(modifier)) {
				return false;
			}
			else if (MOD_PACKAGE.equalsIgnoreCase(notAllowedModifier)
					&& MOD_PACKAGE.equals(modifier)) {
				return false;
			}

		}

		return true;
	}

	/**
	 * @return test class name filters - starts with
	 */
	private Vector<String> getTestClassNameFilterStartsWith() {
		return nameFilterStartsWith;
	}

	/**
	 * @return test class name filters - equals
	 */
	private Vector<String> getTestClassNameFilterEquals() {
		return nameFilterEquals;
	}

	/**
	 * @return test class name filters - ends with
	 */
	private Vector<String> getTestClassNameFilterEndsWith() {
		return nameFilterEndsWith;
	}

	/**
	 * Adds the listener to the page elements.
	 * 
	 * @param page
	 */
	private void addListener(GeneratorWizardMainPage page) {
		page.getBtnSelectAll().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleSelectAll();
			}
		});

		page.getBtnDeselectAll().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleDeselectAll();
			}
		});

		page.getBtnNamefilter().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleNameFilterSelection();
			}
		});

		page.getBtnModifierfilter().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleModifierFilterSelection();
			}
		});

		page.getBtnExistingMethodsFilter().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleExistingMethodsFilterSelection();
			}
		});

		page.getCheckboxTreeViewer().addCheckStateListener(new ICheckStateListener() {
			@Override
			public void checkStateChanged(CheckStateChangedEvent event) {
				Object element = event.getElement();

				try {
					if (element instanceof IMethod) {
						handleMethodSelection(event.getChecked(), (IMethod) element);
					}
					else if (element instanceof IType) {
						if (event.getChecked()) {
							handleSelectAll();
						}
						else {
							handleDeselectAll();
						}
					}
				}
				catch (JavaModelException e) {
					// nothing
				}

				methodSelectionChanged();
			}

			private void handleMethodSelection(boolean checked, IMethod element)
					throws JavaModelException {
				if (element.isConstructor()) {
					return;
				}

				if (checked) {
					if (!checkedMethods.contains(element)) {
						checkedMethods.add(element);
					}
				}
				else {
					checkedMethods.remove(element);
				}

			}
		});

		// toggle buttons
		page.getBtnToggleStandardMethods().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleToggleStandardMethods();
			}
		});

		page.getBtnToggleHooks().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleToggleHooks();
			}
		});

		page.getBtnToggleOther().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleToggleOther();
			}
		});
	}

	/**
	 * Handle the toggle button for the hooks
	 */
	protected void handleToggleHooks() {
		toggleButton(getPage().getBtnCreatetestbasebefore());
		toggleButton(getPage().getBtnCreatetestbaseafter());
		toggleButton(getPage().getBtnBeforemethodcall());
		toggleButton(getPage().getBtnAftermethodcall());
	}

	/**
	 * Handle the toggle button for the other
	 */
	protected void handleToggleOther() {
		toggleButton(getPage().getBtnTestsuites());
		toggleButton(getPage().getBtnLogger());
		toggleButton(getPage().getBtnFailassertion());
	}

	/**
	 * Handle the toggle button for the methods
	 */
	protected void handleToggleStandardMethods() {
		toggleButton(getPage().getBtnSetup());
		toggleButton(getPage().getBtnSetupbeforeclass());
		toggleButton(getPage().getBtnTeardown());
		toggleButton(getPage().getBtnTeardownafterclass());
	}

	/**
	 * Toggles the button.
	 */
	private void toggleButton(Button btn) {
		boolean selection = !btn.getSelection();
		btn.setSelection(selection);
	}

	/**
	 * Is called if the method selection changed.
	 */
	private void methodSelectionChanged() {

		if (getCheckedMethods().size() == 0) {
			getPage().updateStatus("Select at least one method!");
			return;
		}

		getPage().getCheckboxTreeViewer().expandAll();

		getPage().updateStatus(null);
	}

	/**
	 * Handle the name filter selecion.
	 */
	private void handleNameFilterSelection() {
		nameFilterSelected = getPage().getBtnNamefilter().getSelection();
		getPage().getCheckboxTreeViewer().refresh();
		methodSelectionChanged();
	}

	/**
	 * Handle the modifier filer selection.
	 */
	private void handleModifierFilterSelection() {
		modifierFilterSelected = getPage().getBtnModifierfilter().getSelection();
		getPage().getCheckboxTreeViewer().refresh();
		methodSelectionChanged();
	}

	/**
	 * Handle the existing methods filter selection.
	 */
	private void handleExistingMethodsFilterSelection() {
		existingMethodsFilterSelected = getPage().getBtnExistingMethodsFilter().getSelection();
		getPage().getCheckboxTreeViewer().refresh();
		methodSelectionChanged();
	}

	/**
	 * Handle the select all action.
	 */
	private void handleSelectAll() {
		selectAll(true);
		methodSelectionChanged();
	}

	/**
	 * Handle the deselect all action.
	 */
	private void handleDeselectAll() {
		selectAll(false);
		methodSelectionChanged();
	}

	/**
	 * @return base class methods
	 */
	private Vector<IMethod> getBaseClassMethods() {
		if (baseClassMethods == null) {
			baseClassMethods = getModel().getUtmElements().getConstructorsAndMethods()
					.getBaseClassMethods();
		}

		return baseClassMethods;
	}

	/**
	 * Selects or deselects all methods.
	 * 
	 * @param state
	 */
	private void selectAll(boolean state) {
		CheckboxTreeViewer checkboxTreeViewer = getPage().getCheckboxTreeViewer();
		ITreeContentProvider contentProvider = (ITreeContentProvider) checkboxTreeViewer
				.getContentProvider();
		Object[] objects = contentProvider.getElements(checkboxTreeViewer.getInput());

		for (Object object : objects) {
			checkboxTreeViewer.setChecked(object, state);
		}

		for (IMethod method : getBaseClassMethods()) {
			if (viewerFilterMethods.select(null, method.getParent(), method)) {
				if (state) {
					checkedMethods.add(method);
				}
				else {
					checkedMethods.remove(method);
				}
			}
		}

	}

	@Override
	public void initPage() {

		GeneratorWizardMainPage page = getPage();
		Test tmlTest = getModel().getTmlTest();
		List<Method> tmlMethods = null;

		initNameFilters();

		initDefaults(page);

		if (tmlTest != null) {
			tmlMethods = tmlTest.getMethod();

			// initialize settings
			initPageSettings(page, tmlTest.getSettings());

			initTestprio(page, tmlTest);

			// initialize existing methods
			initExistingMethods();
		}

		// initialize method-viewer
		viewerFilterMethods = new ViewerFilterMethods(tmlMethods);
		page.getCheckboxTreeViewer().addFilter(viewerFilterMethods);
		page.getCheckboxTreeViewer().setCheckStateProvider(new CheckStateProvider());
		page.getCheckboxTreeViewer().setInput(testBase);
		page.getCheckboxTreeViewer().expandAll();

		// add listener
		addListener(page);

		methodSelectionChanged();
	}

	/**
	 * Initializes the defaults.
	 * 
	 * @param page
	 */
	private void initDefaults(GeneratorWizardMainPage page) {
		String testProjectPostfix = UTMPreferences.getTestProjectPostfix();
		String baseProjectName = getModel().getUtmElements().getProjects().getBaseProject()
				.getElementName();
		String testProjectName = baseProjectName + testProjectPostfix;

		page.getTxtTestProject().setText(testProjectName);
		page.getSuperClass().setText(UTMPreferences.getTestClassSuperType());
		page.getMethodPrefix().setText(UTMPreferences.getTestMethodPrefix());
		page.getBtnTestsuites().setSelection(true);
	}

	/**
	 * Initializes the page settings.
	 * 
	 * @param page
	 * @param settings
	 */
	private void initPageSettings(GeneratorWizardMainPage page, Settings settings) {
		if (settings == null) {
			return;
		}

		// standard methods
		page.getBtnSetup().setSelection(settings.isSetUp());
		page.getBtnSetupbeforeclass().setSelection(settings.isSetUpBeforeClass());
		page.getBtnTeardown().setSelection(settings.isTearDown());
		page.getBtnTeardownafterclass().setSelection(settings.isTearDownBeforeClass());

		// hooks
		page.getBtnCreatetestbasebefore().setSelection(settings.isHookCreateTestBaseBefore());
		page.getBtnCreatetestbaseafter().setSelection(settings.isHookCreateTestBaseAfter());
		page.getBtnBeforemethodcall().setSelection(settings.isHookBeforeMethodCall());
		page.getBtnAftermethodcall().setSelection(settings.isHookAfterMethodCall());

		// other
		page.getBtnTestsuites().setSelection(settings.isTestsuites());
		page.getBtnFailassertion().setSelection(settings.isFailAssertions());
		page.getBtnLogger().setSelection(settings.isLogger());

	}

	/**
	 * Initializes the existing methods.
	 */
	private void initExistingMethods() {

		for (IMethod baseClassMethod : getBaseClassMethods()) {
			if (containMethod(baseClassMethod, getModel().getTmlTest().getMethod())) {
				checkedMethods.add(baseClassMethod);
			}
		}

	}

	/**
	 * Initializes the test priority.
	 * 
	 * @param page
	 * @param tmlTest
	 */
	private void initTestprio(GeneratorWizardMainPage page, Test tmlTest) {
		page.getBtnPrioHigh().setSelection(false);
		page.getBtnPrioStandard().setSelection(false);
		page.getBtnPrioLow().setSelection(false);

		Testprio testprio = tmlTest.getTestPrio();

		if (testprio != null) {
			switch (testprio) {
			case HIGH:
				page.getBtnPrioHigh().setSelection(true);
				break;
			case DEFAULT:
				page.getBtnPrioStandard().setSelection(true);
				break;
			case LOW:
				page.getBtnPrioLow().setSelection(true);
				break;
			default:
				page.getBtnPrioStandard().setSelection(true);
				break;
			}
		}
		else {
			page.getBtnPrioStandard().setSelection(true);
		}
	}

	@Override
	public void updateModel() {
		Test tmlTest = getModel().getTmlTest();
		GeneratorWizardMainPage page = getPage();

		// test
		if (tmlTest == null) {
			tmlTest = getObjectFactory().createTest();
			getModel().setTmlTest(tmlTest);
		}

		tmlTest.setTestBase(testBase.getPath().toString());
		tmlTest.setTestClass(testClass.getPath().toString());

		// TML-version
		tmlTest.setVersion(TML_VERSION_ACTUAL);

		// test-priority
		if (page.getBtnPrioHigh().getSelection()) {
			tmlTest.setTestPrio(Testprio.HIGH);
		}
		else if (page.getBtnPrioStandard().getSelection()) {
			tmlTest.setTestPrio(Testprio.DEFAULT);
		}
		else if (page.getBtnPrioLow().getSelection()) {
			tmlTest.setTestPrio(Testprio.LOW);
		}

		updateModelSettings(page, tmlTest);

		// methods
		try {
			updateModelMethods(tmlTest);
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Updates the settings in the model from the page.
	 * 
	 * @param page
	 * @param tmlTest
	 */
	private void updateModelSettings(GeneratorWizardMainPage page, Test tmlTest) {
		Settings settings = tmlTest.getSettings();

		if (settings == null) {
			settings = getObjectFactory().createSettings();
			tmlTest.setSettings(settings);
		}

		// standard methods
		settings.setSetUp(page.getBtnSetup().getSelection());
		settings.setSetUpBeforeClass(page.getBtnSetupbeforeclass().getSelection());
		settings.setTearDown(page.getBtnTeardown().getSelection());
		settings.setTearDownBeforeClass(page.getBtnTeardownafterclass().getSelection());

		// hooks
		settings.setHookCreateTestBaseBefore(page.getBtnCreatetestbasebefore().getSelection());
		settings.setHookCreateTestBaseAfter(page.getBtnCreatetestbaseafter().getSelection());
		settings.setHookBeforeMethodCall(page.getBtnBeforemethodcall().getSelection());
		settings.setHookAfterMethodCall(page.getBtnAftermethodcall().getSelection());

		// other
		settings.setTestsuites(page.getBtnTestsuites().getSelection());
		settings.setLogger(page.getBtnLogger().getSelection());
		settings.setFailAssertions(page.getBtnFailassertion().getSelection());
	}

	/**
	 * Updates the methods from the page.
	 * 
	 * @param tmlTest
	 * @throws JavaModelException
	 */
	private void updateModelMethods(Test tmlTest) throws JavaModelException {
		Method tmlMethod;

		// delete old methods
		tmlTest.getMethod().clear();

		// add methods
		HashMap<IMethod, Method> methodMap = new HashMap<IMethod, Method>();
		getModel().setMethodMap(methodMap);

		for (IMethod method : getCheckedMethods()) {
			tmlMethod = getObjectFactory().createMethod();
			tmlTest.getMethod().add(tmlMethod);

			updateModelMethod(method, tmlMethod);

			// save in method-map
			methodMap.put(method, tmlMethod);
		}
	}

	/**
	 * Updates the method from the page.
	 * 
	 * @param method
	 * @param tmlMethod
	 * @throws JavaModelException
	 */
	public void updateModelMethod(IMethod method, Method tmlMethod) throws JavaModelException {
		Result result;
		Param param;
		String returnType;

		tmlMethod.setName(method.getElementName());
		tmlMethod.setModifier(GeneratorUtils.getMethodModifier(method));
		tmlMethod.setStatic(isStatic(method));

		// parameters
		ILocalVariable[] parameters = method.getParameters();

		for (ILocalVariable parameter : parameters) {
			param = getObjectFactory().createParam();
			param.setName(parameter.getElementName());
			param.setType(Signature.getSignatureSimpleName(parameter.getTypeSignature()));

			tmlMethod.getParam().add(param);
		}

		// return type
		returnType = method.getReturnType();
		if (returnType != null && !returnType.equals("V")) {
			result = getObjectFactory().createResult();
			result.setName("result");
			result.setType(Signature.getSignatureSimpleName(returnType));
			tmlMethod.setResult(result);
		}
	}

	/**
	 * @param method
	 * @return true if method is static
	 * @throws JavaModelException
	 */
	private boolean isStatic(IMethod method) throws JavaModelException {
		return Flags.isStatic(method.getFlags());
	}

	/**
	 * Returns the checked methods.
	 * 
	 * @return checked methods
	 */
	public Vector<IMethod> getCheckedMethods() {
		return checkedMethods;
	}

	/**
	 * Returns if the name filter is selected.
	 * 
	 * @return true if the name filter is selected
	 */
	protected boolean isNameFilterSelected() {
		return nameFilterSelected;
	}

	/**
	 * Returns if the modifier filter is selected.
	 * 
	 * @return true if modifier filter is selected
	 */
	protected boolean isModifierFilterSelected() {
		return modifierFilterSelected;
	}

	/**
	 * Returns if the existing methods filter is selected.
	 * 
	 * @return true if the existing methods filter is selected
	 */
	protected boolean isExistingMethodsFilterSelected() {
		return existingMethodsFilterSelected;
	}
}
